#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <time.h>

#include "ApnCamera.h"
#include "ApnFilterWheel.h"
#define LibApogee
#include "libapogee.h"

/* master object */
static CApnCamera *alta;

static void checkalta();

/* connect to the camera and perform any one-time setup.
 * return 0 if ok else -1
 */
int
ApnGlueOpen(unsigned int id)
{
	alta = (CApnCamera *)new CApnCamera();
	if (!alta->InitDriver(id,80,0) || !alta->ResetSystem())
	    return (-1);
	alta->write_LedMode(0); // turn OFF LEDs
/*
	alta->write_LedState (0, Apn_LedState_Expose);
	alta->write_LedState (1, Apn_LedState_AtTemp);
*/
	return (0);
}

/* return the max values for the camera.
 */
void
ApnGlueGetMaxValues (double * /*exptime*/, int *roiw, int *roih, int *osw,
     int *osh, int *binw, int *binh, int *shutter, double *mintemp)
{
	checkalta();

	*roiw = alta->m_ApnSensorInfo->m_ImagingColumns;
	*roih = alta->m_ApnSensorInfo->m_ImagingRows;
	*osw =  alta->m_ApnSensorInfo->m_OverscanColumns;
	*osh =  alta->m_ApnSensorInfo->m_OverscanRows;
	*binw = alta->read_MaxBinningH();
	*binh = alta->read_MaxBinningV();

	*shutter = 1;		/* TODO */

	*mintemp = alta->m_ApnSensorInfo->m_CoolingSupported ? -30 : 0; /*TODO*/
}

void ApnGlueGetGeom(double *pixX, double *pixY){
	*pixX = alta->m_ApnSensorInfo->m_PixelSizeX;
	*pixY = alta->m_ApnSensorInfo->m_PixelSizeY;
}

/* set parameters for subsequent exposures.
 * if anything can't be done return -1 with explanation in whynot[], else 0.
 * also return final image size in net binned pixels, allowing for overscan,
 * binning etc.
 */
int
ApnGlueSetExpGeom (int roiw, int roih, int osw, int osh, int binw, int binh, int roix, int roiy, int *impixw, int *impixh, char whynot[])
{
	int maxw, maxh;

	checkalta();

	maxw = alta->m_ApnSensorInfo->m_ImagingColumns;
	maxh = alta->m_ApnSensorInfo->m_ImagingRows;

	/* be kind */
	if (!binw) binw = 1;
	if (!binh) binh = 1;
	if (!roiw) roiw = maxw;
	if (!roih) roih = maxh;

	/* check maxes */
	if (roiw > maxw) {
	    sprintf (whynot, "Max width is %d", maxw);
	    return (-1);
	}
	if (roih > maxh) {
	    sprintf (whynot, "Max height is %d", maxh);
	    return (-1);
	}

	/* check overscan */
	if (osw > 0 || osh > 0) {
	    int maxosw = alta->read_OverscanColumns();
	    int maxosh = alta->m_ApnSensorInfo->m_OverscanRows;

	    if (osw > maxosw) {
		sprintf (whynot, "Max overscan columns is %d", maxosw);
		return (-1);
	    }
	    if (osh > maxosh) {
		sprintf (whynot, "Max overscan rows is %d", maxosh);
		return (-1);
	    }
	    if (roix > 0 || roiw < maxw || roiy > 0 || roih < maxh) {
		sprintf (whynot, "Can not overscan with windowing");
		return (-1);
	    }

	    roiw += osw;
	    roih += osh;
	    alta->write_DigitizeOverscan(1);
	} else
	    alta->write_DigitizeOverscan(0);


	/* ok */
	alta->write_RoiStartX (roix);
	alta->write_RoiStartY (roiy);
	alta->write_RoiPixelsH (roiw/binw);
	alta->write_RoiPixelsV (roih/binh);

	alta->write_RoiBinningH (binw);
	alta->write_RoiBinningV (binh);

	*impixw = roiw/binw;
	*impixh = roih/binh;

	return (0);
}

/* start an exposure with the given duration (secs) and whether to open shutter.
 * update *exptime with the actual exposure time (may have been bumped to min).
 * return 0 if ok, else -1
 */
int
ApnGlueStartExp (double *exptime, int shutter)
{
	double minsecs;

	checkalta();

	minsecs = alta->m_ApnSensorInfo->m_MinSuggestedExpTime/1000.0;
	if (*exptime < minsecs)
	    *exptime = minsecs;

	alta->write_ImageCount(1);
	return (alta->Expose(*exptime, shutter) ? 0 : -1);
}

/* abort an exposure */
void
ApnGlueExpAbort(void)
{
	checkalta();

	alta->StopExposure(0);

}

/* return 1 if the current exposure is complete, else 0
 */
int
ApnGlueExpDone(void)
{
	checkalta();

	return (alta->read_ImagingStatus() == Apn_Status_ImageReady);
}


/* read pixels from the camera into the given buffer of length nbuf.
 * return 0 if ok else -1 with reason in whynot[].
 */
int
ApnGlueReadPixels (unsigned short *buf, int nbuf, char whynot[])
{
	unsigned short w, h;
	unsigned long c, r;

	checkalta();

	r = alta->GetImageData (buf, w, h, c);

	if (r != CAPNCAMERA_SUCCESS) 
	{
	    sprintf (whynot, "GetImageData returned %ld\n", r);
	    return (-1);
	}

	if (w*h != nbuf) 
	{
	    sprintf (whynot, "Expecting %d pixels but found %d", nbuf, w*h);
	    return (-1);
	}

	return (0);
}

/* set cooler target to given value, C
 * turn cooler off it target is 0.
 */
void
ApnGlueSetTemp (double C)
{
	checkalta();

	if (C == 0) {
	    alta->write_CoolerEnable(0);
	} else {
	    alta->write_CoolerEnable(1);
	    alta->write_CoolerSetPoint(C);
	}
}


/* fetch current cooler temp, C, and return status:
 *   0 = off
 *   1 = ramping to set point
 *   2 = at set point
 */
int
ApnGlueGetTemp (double *Cp)
{
	int status;

	checkalta();

	*Cp = alta->read_TempCCD();
	status = alta->read_CoolerStatus();
	if (status > 2)
	    status = 2;
	return (status);
}

/* set fan speed: 0..3 */
void
ApnGlueSetFan (int speed)
{
	checkalta();
	alta->write_FanMode(0);		/* helps to do this first */
	alta->write_FanMode(speed&3);
}

int ApnGlueGetFan(){
	checkalta();
	int mode = alta->read_FanMode();
	return mode;
}
/* return pointers to sensor and camera names.
 * N.B. caller should neither modify nor free these strings.
 */
void
ApnGlueGetName(char **sensor, char **camera)
{
	*sensor = alta->m_ApnSensorInfo->m_Sensor;
	*camera = alta->m_ApnSensorInfo->m_CameraModel;
}

/* check that the alta object has been created, else complain and exit.
 */
static void
checkalta()
{
	if (!alta) {
	    fprintf (stderr, "Bug! Alta used before open\n");
	    exit(1);
	}
}


// Reset camera
void ApnGlueReset(){
	checkalta();
	alta->ResetSystem();
}
// Close Camera device
void ApnGlueClose(){
	if(alta){
		alta->CloseDriver();
		delete alta;
	}
}
// Pause or resume timer (flag == 1 - pause, 0 - resume)
void ApnGluePauseTimer(int flag){
	checkalta();
	alta->PauseTimer(flag);
}
// Stop exposure & take image
int ApnGlueStopExposure(){
	checkalta();
	return alta->StopExposure(1);
}
// Power off, if OK return 0
int ApnGluePowerDown(){
	checkalta();
	return (!alta->PowerDown());
}
// Resume camera
void ApnGluePowerResume(){
	checkalta();
	alta->Resume();
}
// If shutter opend returns 1
int ApnGlueReadShutter(){
	checkalta();
	return alta->read_ShutterState();
}
// Open shutter (flag!=0) or close it(flag==0)
void ApnGlueOpenShutter(int flag){
	checkalta();
	alta->write_ForceShutterOpen(flag);
}
// Disable (flag!=1) or enable post expose flushing
void ApnGlueDisablePostExpFlushing(int flag){
	checkalta();
	alta->write_DisablePostExposeFlushing(flag);
}
// Set (flag!=0) or reset preexposure IR-flashing
void ApnGluePreFlash(int flag){
	checkalta();
	alta->write_PreFlashEnable(flag);
}
// Write Camera Mode
void ApnGlueWriteCamMode(Apn_CameraMode CameraMode){
	checkalta();
	alta->write_CameraMode(CameraMode);
}
// Set data bits
void ApnGlueSetDatabits(Apn_Resolution BitResolution){
	checkalta();
	alta->write_DataBits(BitResolution);
	if(BitResolution == Apn_Resolution_TwelveBit) alta->InitTwelveBitAD();
	else alta->InitAscentSixteenBitAD();
}
/* Get setpoint temperature; return 1 if cooler is on, 0 if cooler is off;
	stat == 0 - clamp to setpoint, 1 - at setpoint*/
int ApnGlueReadSetPoint(double *temp, int *stat){
	checkalta();
	Apn_CoolerStatus S = alta->read_CoolerStatus();
	if(S == Apn_CoolerStatus_Off) return 0;
	if(stat) *stat = (S == Apn_CoolerStatus_AtSetPoint) ? 1:0;
	if(temp) *temp = alta->read_CoolerSetPoint();
	return 1;
}
// Get heatsink temperature
double ApnGlueGetHotTemp(){
	checkalta();
	return alta->read_TempHeatsink();
}
// Get readout speed
unsigned short ApnGlueGetSpeed(){
	checkalta();
	return alta->read_DigitizationSpeed();
}
// Set readout speed
void ApnGlueSetSpeed(unsigned short Sp){
	checkalta();
	alta->write_DigitizationSpeed(Sp);
}

/***********************************************
 *                   FILTER                    *
 ***********************************************/
 
static CApnFilterWheel *wheel;
static void checkwheel();

// Init wheel, return 0 if init OK
int ApnGlueWheelOpen(unsigned int id, Apn_Filter type){
	wheel = (CApnFilterWheel*) new CApnFilterWheel();
	if(!wheel->Init(type, id)) return -1;
	return 0;
}
// Close device
void ApnGlueWheelClose(){
	if(wheel) wheel->Close();
	delete wheel;
}
// Return 0 if ready, 1 if moving or absent
int ApnGlueWheelGetStatus(){
	Apn_FilterStatus st;
	checkwheel();
	if(!wheel->GetStatus(&st) || st != Apn_FilterStatus_Ready) return 1;
	return 0;
}
// Return amount of positions or -1 if err
int ApnGlueWheelGetMaxPos(){
	unsigned long m;
	checkwheel();
	//printf("max_pos: %ld\n", wheel->m_MaxPositions);
	//if(!wheel->GetMaxPositions(&m)) return -1;
	return (int) wheel->m_MaxPositions;
}
// Set position pos; return 0 if OK
int ApnGlueWheelSetPos(int pos){
	checkwheel();
	if(pos < 0 || !wheel->SetPosition((unsigned long) pos)) return -1;
	return 0;
}
// Return current position or -1 if err
int ApnGlueWheelGetPos(){
	unsigned long m;
	checkwheel();
	if(!wheel->GetPosition(&m)) return -1;
	return (int) m;
}

static void checkwheel(){
	if (!wheel){
		fprintf(stderr, "Bug! Wheel used before open\n");
		exit(1);
	}
}
